package com.ld.shieldsb.common.core.io.zip;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.List;

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;

import com.ld.shieldsb.common.core.util.StringUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FileZip {
    private FileZip() {
        throw new IllegalStateException("该类不可初始化");
    }

    private static final String ERROR_DIR_NOT_EXISTS = "文件压缩失败，目录 {} 不存在!";
    private static final String ERROR_ZIP_FAILED = "文件压缩失败：";

    /**
     * 将文件压缩到文件中（不保留目录结构）
     * 
     * @Title zipFiles
     * @author 吕凯
     * @date 2019年2月13日 下午4:14:08
     * @param srcFiles
     * @param outFile
     * @return
     * @throws FileNotFoundException
     *             boolean
     */
    public static boolean zipFiles(List<File> srcFiles, File outFile) throws FileNotFoundException {
        return zipFiles(srcFiles, new FileOutputStream(outFile));
    }

    /**
     * 将文件压缩到流中（不保留目录结构）
     * 
     * @Title zipFiles
     * @author 吕凯
     * @date 2019年2月13日 下午4:13:38
     * @param srcFiles
     * @param outputStram
     * @return boolean
     */
    public static boolean zipFiles(List<File> srcFiles, OutputStream outputStram) {
        FileInputStream zipSource = null;
        BufferedInputStream bufferStream = null;

        try (ZipOutputStream zipStream = new ZipOutputStream(outputStram)) {
            for (File file : srcFiles) {
                zipSource = null;// 将源头文件格式化为输入流
                zipSource = new FileInputStream(file);

                // 压缩条目不是具体独立的文件，而是压缩包文件列表中的列表项，称为条目，就像索引一样
                ZipEntry zipEntry = new ZipEntry(file.getName());
                zipStream.putNextEntry(zipEntry);// 定位到该压缩条目位置，开始写入文件到压缩包中

                bufferStream = new BufferedInputStream(zipSource, 1024 * 10);// 输入缓冲流
                int read = 0;
                byte[] bufferArea = new byte[1024 * 10];// 读写缓冲区

                // 在任何情况下，b[0] 到 b[off] 的元素以及 b[off+len] 到 b[b.length-1] 的元素都不会受到影响。这个是官方API给出的read方法说明，经典！
                while ((read = bufferStream.read(bufferArea, 0, 1024 * 10)) != -1) {
                    zipStream.write(bufferArea, 0, read);
                }
            }
        } catch (Exception e) {
            log.error("压缩文件出错！", e);
            return false;
        } finally {
            // 关闭流
            if (null != bufferStream) {
                try {
                    bufferStream.close();
                } catch (Exception e) {
                    log.error("", e);
                }
            }
            if (null != zipSource) {
                try {
                    zipSource.close();
                } catch (Exception e) {
                    log.error("", e);
                }
            }
            if (null != outputStram) {
                try {
                    outputStram.close();
                } catch (Exception e) {
                    log.error("", e);
                }
            }
        }
        return true;
    }

    /**
     * 压缩目录下的文件
     * 
     * @param srcDirName
     *            压缩的根目录
     * @param fileName
     *            根目录下的待压缩的文件名或文件夹名，其中*或""表示跟目录下的全部文件
     * @param descFileName
     *            目标zip文件
     */
    public static void zipFiles(String srcDirName, String fileName, String descFileName) {
        // 判断目录是否存在
        if (srcDirName == null) {
            log.debug(ERROR_DIR_NOT_EXISTS);
            return;
        }
        File fileDir = new File(srcDirName);
        if (!fileDir.exists() || !fileDir.isDirectory()) {
            log.debug(ERROR_DIR_NOT_EXISTS, srcDirName);
            return;
        }
        String dirPath = fileDir.getAbsolutePath();
        File descFile = new File(descFileName);
        try (ZipOutputStream zouts = new ZipOutputStream(new FileOutputStream(descFile));) {

            if ("*".equals(fileName) || "".equals(fileName)) { // 全部
                zipDirectoryToZipFile(dirPath, fileDir, zouts);
            } else {
                File file = new File(fileDir, fileName);
                if (file.isFile()) {
                    zipFilesToZipFile(dirPath, file, zouts);
                } else {
                    zipDirectoryToZipFile(dirPath, file, zouts);
                }
            }
//            zouts.close();
            log.debug(descFileName + " 文件压缩成功!");
        } catch (Exception e) {
            log.error(ERROR_ZIP_FAILED + e.getMessage(), e);
        }

    }

    /**
     * 
     * 压缩目录下的文件到流
     * 
     * @Title zipFiles
     * @author 吕凯
     * @date 2018年3月31日 下午2:14:36
     * @param srcDirName
     *            压缩的根目录
     * @param fileName
     *            根目录下的待压缩的文件名或文件夹名，其中*或""表示跟目录下的全部文件
     * @param outputStram
     *            压缩到的流 void
     */
    public static void zipFiles(String srcDirName, String fileName, OutputStream outputStram) {
        // 判断目录是否存在
        if (srcDirName == null) {
            log.warn(ERROR_DIR_NOT_EXISTS);
            return;
        }
        File fileDir = new File(srcDirName);
        if (!fileDir.exists() || !fileDir.isDirectory()) {
            log.warn(ERROR_DIR_NOT_EXISTS, srcDirName);
            return;
        }
        String dirPath = fileDir.getAbsolutePath();
        try (ZipOutputStream zouts = new ZipOutputStream(outputStram);) {
            if ("*".equals(fileName) || "".equals(fileName)) {
                zipDirectoryToZipFile(dirPath, fileDir, zouts);
            } else {
                File file = new File(fileDir, fileName);
                if (file.isFile()) { // 文件
                    zipFilesToZipFile(dirPath, file, zouts);
                } else { // 目录
                    zipDirectoryToZipFile(dirPath, file, zouts);
                }
            }
//            zouts.close();
            log.debug("输出流 文件压缩成功!");
        } catch (Exception e) {
            log.error(ERROR_ZIP_FAILED + e.getMessage(), e);
        }

    }

    /**
     * 压缩传入的文件到流
     * 
     * @Title zipFiles
     * @author 吕凯
     * @date 2019年2月13日 下午4:31:03
     * @param srcDirName
     * @param srcFiles
     * @param outputStram
     *            void
     */
    public static void zipFiles(String srcDirName, List<File> srcFiles, OutputStream outputStram) {
        // 判断目录是否存在
        if (srcDirName == null) {
            log.warn(ERROR_DIR_NOT_EXISTS);
            return;
        }
        File fileDir = new File(srcDirName);
        if (!fileDir.exists() || !fileDir.isDirectory()) {
            log.warn(ERROR_DIR_NOT_EXISTS, srcDirName);
            return;
        }
        String dirPath = fileDir.getAbsolutePath();
        try (ZipOutputStream zouts = new ZipOutputStream(outputStram);) {
            for (File file : srcFiles) {
                zipFilesToZipFile(dirPath, file, zouts);
            }
            log.debug("输出流 文件压缩成功!");
        } catch (Exception e) {
            log.error(ERROR_ZIP_FAILED + e.getMessage(), e);
        }

    }

    /**
     * 解压缩ZIP文件，将ZIP文件里的内容解压到descFileName目录下 刘金浩 加入解压时可以在设置编码格式
     * 
     * @param zipFileName
     *            需要解压的ZIP文件
     * @param descFileName
     *            目标文件
     */
    public static boolean unZipFiles(String zipFileName, String descFileName) {

        return unZipFiles(zipFileName, descFileName, null);
    }

    /**
     * 解压缩ZIP文件，将ZIP文件里的内容解压到descFileName目录下
     * 
     * @param zipFileName
     *            需要解压的ZIP文件
     * @param descFileName
     *            目标文件
     */
    public static boolean unZipFiles(String zipFileName, String descFileName, String encoding) {
        String descFileNames = descFileName;
        if (!descFileNames.endsWith(File.separator)) {
            descFileNames = descFileNames + File.separator;
        }
        // 根据ZIP文件创建ZipFile对象
        ZipFile zipFile = null;
        try {
            if (StringUtils.isNotEmpty(encoding)) {
                zipFile = new ZipFile(zipFileName, encoding);
            } else {
                zipFile = new ZipFile(zipFileName);
            }
            ZipEntry entry = null;
            String entryName = null;
            String descFileDir = null;
            byte[] buf = new byte[4096];
            int readByte = 0;
            // 获取ZIP文件里所有的entry
            Enumeration<ZipEntry> enums = zipFile.getEntries();
            // 遍历所有entry
            while (enums.hasMoreElements()) {
                entry = enums.nextElement();
                // 获得entry的名字
                entryName = entry.getName();
                descFileDir = descFileNames + entryName;
                if (entry.isDirectory()) {
                    // 如果entry是一个目录，则创建目录
                    new File(descFileDir).mkdirs();
                    continue;
                } else {
                    // 如果entry是一个文件，则创建父目录
                    new File(descFileDir).getParentFile().mkdirs();
                }
                File file = new File(descFileDir);
                // 打开文件输出流
                try (OutputStream os = new FileOutputStream(file); InputStream is = zipFile.getInputStream(entry)) {
                    // 从ZipFile对象中打开entry的输入流
                    while ((readByte = is.read(buf)) != -1) {
                        os.write(buf, 0, readByte);
                    }
                }
            }
            if (log.isDebugEnabled()) {
                log.debug("文件解压成功!");
            }
            return true;
        } catch (Exception e) {
            log.error("文件解压失败", e);
            return false;
        } finally {
            if (zipFile != null) {
                try {
                    zipFile.close();
                } catch (IOException e) {
                    log.error("", e);
                }
            }
        }
    }

    /**
     * 将目录压缩到ZIP输出流
     * 
     * @param dirPath
     *            目录路径
     * @param fileDir
     *            目录下的文件信息(目录)
     * @param zouts
     *            输出流
     */
    private static void zipDirectoryToZipFile(String dirPath, File fileDir, ZipOutputStream zouts) {
        if (fileDir.isDirectory()) {
            File[] files = fileDir.listFiles();
            // 空的文件夹
            if (files.length == 0) {
                // 目录信息
                ZipEntry entry = new ZipEntry(getEntryName(dirPath, fileDir));
                try {
                    zouts.putNextEntry(entry);
                    zouts.closeEntry();
                } catch (Exception e) {
                    log.error("", e);
                }
                return;
            }

            for (int i = 0; i < files.length; i++) {
                if (files[i].isFile()) {
                    // 如果是文件，则调用文件压缩方法
                    zipFilesToZipFile(dirPath, files[i], zouts);
                } else {
                    // 如果是目录，则递归调用
                    zipDirectoryToZipFile(dirPath, files[i], zouts);
                }
            }
        }
    }

    /**
     * 将文件压缩到ZIP输出流
     * 
     * @param dirPath
     *            目录路径
     * @param file
     *            文件
     * @param zouts
     *            输出流
     */
    private static void zipFilesToZipFile(String dirPath, File file, ZipOutputStream zouts) {
        ZipEntry entry = null;
        if (file.isFile()) {
            // 创建一个文件输入流
            // 输入缓冲流
            try (FileInputStream fin = new FileInputStream(file);
                    BufferedInputStream bufferStream = new BufferedInputStream(fin, 1024 * 10);) {
                // 创建一个ZipEntry,压缩条目不是具体独立的文件，而是压缩包文件列表中的列表项，称为条目，就像索引一样
                entry = new ZipEntry(getEntryName(dirPath, file));
                // 存储信息到压缩文件,定位到该压缩条目位置，开始写入文件到压缩包中
                zouts.putNextEntry(entry);
                // 复制字节到压缩文件
                int read = 0;
                byte[] bufferArea = new byte[1024 * 10];// 读写缓冲区

                // 在任何情况下，b[0] 到 b[off] 的元素以及 b[off+len] 到 b[b.length-1] 的元素都不会受到影响。这个是官方API给出的read方法说明，经典！
                while ((read = bufferStream.read(bufferArea, 0, 1024 * 10)) != -1) {
                    zouts.write(bufferArea, 0, read);
                }
                zouts.closeEntry();
                if (log.isDebugEnabled()) {
                    log.debug("添加文件 " + file.getAbsolutePath() + " 到zip文件中!");
                }
            } catch (Exception e) {
                log.error("", e);
            }
        }
    }

    /**
     * 获取待压缩文件在ZIP文件中entry的名字，即相对于跟目录的相对路径名
     * 
     * @param dirPat
     *            根目录名
     * @param file
     *            entry文件名
     * @return
     */
    private static String getEntryName(String dirPath, File file) {
        String dirPaths = dirPath;
        if (!dirPaths.endsWith(File.separator)) {
            dirPaths = dirPaths + File.separator;
        }
        String filePath = file.getAbsolutePath();
        // 对于目录，必须在entry名字后面加上"/"，表示它将以目录项存储
        if (file.isDirectory()) {
            filePath += "/";
        }
        int index = filePath.indexOf(dirPaths);

        return filePath.substring(index + dirPaths.length());
    }
}